use rbatis::crud::CRUD;
use rbatis::rbatis::Rbatis;

use chrono::prelude::*;

use crate::dbs::sqlite::Peer;
use crate::features::ddd::Aggregate;
use crate::features::events::modelsdbo::Event;
use crate::features::events::services::{self};
use crate::features::gfilter::IntFilterIn;
use crate::features::gfilter::StringExactFilterIn;
use crate::features::tasks::ddd::{self, TaskConfig, TaskSnapshot};
use crate::features::tasks::modelsdbo::{TaskDBO, TaskDiffProbaDBO};
use crate::features::tasks::modelsgqo::TaskFilterIn;
use crate::features::tasks::repository::{self};
use crate::features::tasks::{EBP_PROBA_MAX, MAX_EBP_DIFFICULTY};

// 查询所有任务：EB Task's
pub async fn get_tasks_by(
  rbatis_pool: &Rbatis,
  my_peer: &Peer,
  filter: Option<TaskFilterIn>,
) -> Result<Vec<TaskDBO>, rbatis::Error> {
  let mut wrapper = rbatis_pool.new_wrapper();

  wrapper = wrapper.eq("assignee_id", &my_peer.login_user_id);

  if filter.is_some() {
    let filter = filter.unwrap();

    match filter.subject {
      Some(StringExactFilterIn { ref eq, ref lt, .. }) => {
        match eq {
          Some(v) => wrapper = wrapper.and().eq("subject", v),
          _ => {}
        };
        match lt {
          Some(v) => wrapper = wrapper.and().lt("subject", v),
          _ => {}
        }
      }
      _ => {}
    };

    match filter.ebp_difficulty {
      Some(IntFilterIn { ref eq, ref lt, .. }) => {
        match eq {
          Some(v) => match v {
            0..=MAX_EBP_DIFFICULTY => wrapper = wrapper.and().eq("ebp_difficulty", v),
            _ => {}
          },
          _ => {}
        };
        match lt {
          Some(v) => match v {
            0..=MAX_EBP_DIFFICULTY => wrapper = wrapper.and().lt("ebp_difficulty", v),
            _ => {}
          },
          _ => {}
        }
      }
      _ => {}
    };

    match filter.ebp_recalltime {
      Some(IntFilterIn { ref eq, ref lt, .. }) => {
        match eq {
          Some(v) => wrapper = wrapper.and().eq("ebp_recalltime", v),
          _ => {}
        };
        match lt {
          Some(v) => wrapper = wrapper.and().lt("ebp_recalltime", v),
          _ => {}
        }
      }
      _ => {}
    };
  }

  let result = rbatis_pool
    .fetch_list_by_wrapper(
      wrapper
        .order_by(false, &["ebp_difficulty", "ebp_forgetproba"])
        .limit(200),
    )
    .await;

  result
}

// command: answer task quizitem
pub async fn answer_task_quizitem(
  rbatis_pool: &Rbatis,
  my_peer: &Peer,
  task_id: &str,
  std_answer: &str,
  answer: &str,
  seconds: Option<u32>,
) -> Result<TaskDBO, rbatis::Error> {
  // load TaskConfig from db

  let dfpbs_dbo = repository::select_all_task_diff_proba(rbatis_pool).await;
  let mut dfpbs: Vec<TaskDiffProbaDBO> = vec![];
  match dfpbs_dbo {
    Ok(v) => dfpbs = v,
    _ => {
      println!("Error: no data found in table  TaskDiffProbaDBO");
    }
  }

  // load TaskAggreate from db
  let task_dbo = repository::find_task_by_id(rbatis_pool, task_id).await;
  if task_dbo.is_ok() {
    let mut task_agg = ddd::TaskAggregate::init(
      &TaskSnapshot {
        task: task_dbo.unwrap(),
        ..Default::default()
      },
      &Vec::new(),
      &TaskConfig {
        dfpbs: dfpbs,
        ..Default::default()
      },
    );

    let evts = task_agg.answer(my_peer, std_answer, answer, seconds);
    println!("answer evts={:?}", evts);

    if evts.is_ok() {
      let evts = evts.unwrap();
      for evt in evts.iter() {
        // 1. save events first before apply the event to aggregate
        let saved =
          services::append(rbatis_pool, my_peer, task_id, &Event::Task(evt.to_owned())).await;
        if saved.is_ok() {
          // 2. then apply event to the aggregate
          task_agg = task_agg.apply(evt);
        } else {
          task_agg = task_agg.apply(evt); // only for testing cause rbatis v3.x bug
          println!("saved failed? saved={:?}", saved);
        }
      }
      println!("1. apply events task_agg={:?}", task_agg);

      // 3. upsert snapshot after applying events
      let upserted = repository::upsert_snapshot(rbatis_pool, task_agg.get_snapshot()).await;
      println!("upsert_snapshot upserted={:?}", upserted);
    }
  }

  // let _ = caculate_tasks_ebp(rbatis_pool, my_peer, None).await;
  repository::find_task_by_id(rbatis_pool, task_id).await
}

///
/// caculate task ebp: ebp_forgetproba, ebp_recalltime
///
pub async fn caculate_tasks_ebp(
  rbatis_pool: &Rbatis,
  my_peer: &Peer,
  filter: Option<TaskFilterIn>,
) -> Result<String, rbatis::Error> {
  let dfpbs_ = repository::select_all_task_diff_proba(rbatis_pool).await;
  let mut dfpbs: Vec<TaskDiffProbaDBO> = vec![];
  if dfpbs_.is_ok() {
    dfpbs = dfpbs_.unwrap();
    if dfpbs.len() <= 0 {
      println!("Error: no data found in table  TaskDiffProbaDBO");
      return Ok("done refresh tasks' ebp_recallprob & ebp_recalltime".to_string());
    }
  }

  let mut wrapper = rbatis_pool.new_wrapper();

  wrapper = wrapper.eq("assignee_id", &my_peer.login_user_id);

  if filter.is_some() {
    let filter = filter.unwrap();

    match filter.subject {
      Some(StringExactFilterIn { ref eq, ref lt, .. }) => {
        match eq {
          Some(v) => wrapper = wrapper.and().eq("subject", v),
          _ => {}
        };
        match lt {
          Some(v) => wrapper = wrapper.and().lt("subject", v),
          _ => {}
        }
      }
      _ => {}
    };
  }

  let now: DateTime<Utc> = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
  wrapper = wrapper.and().le("ebp_recalltime", now.clone().timestamp());

  let tasks_dbo: Result<Vec<TaskDBO>, rbatis::Error> = rbatis_pool
    .fetch_list_by_wrapper(
      wrapper
        .order_by(false, &["ebp_difficulty", "ebp_forgetproba"])
        .limit(200),
    )
    .await;

  match tasks_dbo {
    Ok(v) => {
      println!("going to update_task_ebp={}", v.len());
      for task_dbo in v {
        let rs = update_task_ebp(rbatis_pool, my_peer, &task_dbo, &dfpbs, now.timestamp()).await;
      }
    }
    _ => {}
  }

  Ok("done refresh tasks' ebp_recallprob & ebp_recalltime".to_string())
}

async fn update_task_ebp(
  rbatis_pool: &Rbatis,
  my_peer: &Peer,
  task_dbo: &TaskDBO,
  sorted_dfpbs: &Vec<TaskDiffProbaDBO>,
  now_ts: i64,
) -> Result<String, rbatis::Error> {
  let elapsed_seconds = now_ts - task_dbo.ebp_recalltime;
  //println!("update_task_ebp.task_dbo={:?} now_ts={} elapsed_seconds={}", task_dbo, now_ts,elapsed_seconds);
  //println!("sorted_dfpbs={:?}",sorted_dfpbs);

  if task_dbo.ebp_forgetproba < EBP_PROBA_MAX {
    for dfpb in sorted_dfpbs {
      if dfpb.difficulty == task_dbo.ebp_difficulty
        && dfpb.probability > task_dbo.ebp_forgetproba
        && dfpb.interval < elapsed_seconds
      {
        let upd = TaskDBO {
          ebp_forgetproba: dfpb.probability,
          ebp_recalltime: now_ts + dfpb.interval,
          ..task_dbo.to_owned()
        };
        let _ = repository::update_task(rbatis_pool, &upd).await;
        break;
      }
    }
  };
  Ok(format!(
    "Done updated forgetproba&recalltime for task={}",
    task_dbo.id
  ))
}

pub fn find_in_task_diffprob(
  p_diff: usize,
  p_prob: usize,
  dfpb_dbos: &Vec<TaskDiffProbaDBO>,
) -> Option<TaskDiffProbaDBO> {
  let mut dfpb_dbo: Option<TaskDiffProbaDBO> = None;
  for dbo in dfpb_dbos {
    if dbo.difficulty == p_diff && dbo.probability == p_prob {
      dfpb_dbo = Some(dbo.to_owned());
    }
  }
  dfpb_dbo
}
